import * as THREE from 'three';
import {
  AmbientLight,
  AxesHelper,
  BufferGeometry,
  Clock,
  DirectionalLight,
  Geometry,
  Material,
  MathUtils,
  Matrix3,
  Mesh,
  PlaneBufferGeometry,
  Points,
  PointsMaterial,
  SphereBufferGeometry,
  Texture,
  Vector2,
  Vector3,
} from 'three';
import BaseThreeApp from '../BaseThreeApp';
import DebugInfo from '../DebugInfo';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; 
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

const worldCenter = new Vector3(0, 0, 0);

class _12Particles extends BaseThreeApp {
  public scene: THREE.Scene;
  public camera: THREE.Camera;
  public renderer: THREE.WebGLRenderer;
  public controller: OrbitControls;
  public geos: (Geometry | BufferGeometry)[] = [];
  public mats: Material[] = [];
  public texs: Texture[] = [];
  public clock: Clock;


  private delay: number;
  private isDown: boolean;
  private readyCount: number;
  private orginVerts: Vector3[];
  private vertices: Vector3[];
  private particles: Points;
  private particleGeo: Geometry;  

  start() {
    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera(
      60,
      this.clientWidth / this.clientHeight,
      0.1,
      3000
    );
    this.resetCamera();

    const material = new THREE.MeshLambertMaterial({ color: 0x00ffff });
    this.mats.push(material);
    const buf = new SphereBufferGeometry(0.1);
    this.geos.push(buf);
    this.scene.add(new Mesh(buf, material));  
    const axisHelp = new AxesHelper(10);
    this.geos.push(axisHelp.geometry);
    this.scene.add(axisHelp);
    this.controller = new OrbitControls(this.camera, this.element);
    this.controller.target.copy(worldCenter);
    this.controller.update();

    this.renderer = new THREE.WebGLRenderer({
      canvas: this.element,
    });
    this.renderer.setDrawingBufferSize(this.clientWidth, this.clientHeight, 1);

    const dirLight = new DirectionalLight(0xffffff, 0.6);
    dirLight.position.set(1000, 1000, 1000);
    dirLight.target.position.set(0, 0, 0);
    this.scene.add(dirLight);

    const dirLight2 = new DirectionalLight(0xffffff, 0.3);
    dirLight2.position.set(-1000, -1000, -1000);
    this.scene.add(dirLight2);

    const ambient = new AmbientLight(0xffffff, 0.2);
    this.scene.add(ambient);

    const planeBuf = new PlaneBufferGeometry(1000, 1000, 500, 500);
    this.geos.push(planeBuf);
    const matPlane = new PointsMaterial({ color: 0x223333, size: 0.1 });
    const plane = new Points(planeBuf, matPlane);
    plane.rotation.x = MathUtils.degToRad(90);
    this.scene.add(plane);

    const loader = new GLTFLoader();
    loader.setPath('models/duck/');
    loader.load('Duck.gltf', (gltf)=>{
      this.vertices = [];
      gltf.scene.updateWorldMatrix(false, true);
      gltf.scene.traverse(obj=>{
        if(obj instanceof Mesh){
          if (obj.geometry instanceof Geometry) {
            this.vertices.push(
              ...obj.geometry.vertices.map((v) =>
                new Vector3().copy(v)
              )
            );
          } else if (
            obj.geometry instanceof BufferGeometry
          ) {
            const buf = obj.geometry;
            const bufVec = buf.getAttribute('position')
              .array;
            const matrix = obj.matrixWorld;
            for (let i = 0; i < bufVec.length / 3; i++) {
              this.vertices.push(
                new Vector3(
                  bufVec[i * 3],
                  bufVec[i * 3 + 1],
                  bufVec[i * 3 + 2]
                ).applyMatrix4(matrix)
              );
            }
          }
          this.geos.push(obj.geometry);
          this.mats.push(obj.material);
        }
      })

      const pointGeo = new Geometry();
      this.geos.push(pointGeo);
      this.orginVerts = this.vertices.map(v => new Vector3().copy(v))
      pointGeo.vertices.push(...this.vertices);
      const mat = new PointsMaterial({color: 0xaabb22, size: 0.01})
      this.particles = new Points(pointGeo, mat);
      this.scene.add(this.particles)
      this.readyCount = 0;
      this.isDown = true;
      this.particleGeo = pointGeo;

      this.scene.add(gltf.scene);
      gltf.scene.position.set(2, -0.1, 2);
    })


    this.clock = new Clock(true);
    this.isRunning = true;
    requestAnimationFrame(this.render.bind(this));
  }
  
  public render(time: number){
    if (!this.isRunning || this.element === null) {
      return;
    }
    DebugInfo.Instance.begin();

    this.updateParticles();
    
    this.renderer.render(this.scene, this.camera);
    DebugInfo.Instance.end();
    requestAnimationFrame(this.render.bind(this));
  }

  private updateParticles():void{
    if(!this.particles){
      return;
    }
    const delay = this.clock.getDelta();
    if(this.delay > 0){
      this.delay -= delay;
    } else if(this.isDown){
      for (const v of this.vertices) {
        if(v.y > 0){
          v.y -= (Math.random() * 0.5 + 0.5) * delay;
          if(v.y <= 0){
            this.readyCount++;
          }
        }
      }
      if(this.readyCount >= this.vertices.length){
        this.isDown = false;
        this.delay = 2;
        this.readyCount = 0
      }
      this.particleGeo.verticesNeedUpdate = true;
    } else {

      for (let i = 0; i < this.vertices.length; i++) {
        const v = this.vertices[i];
        const orginVert = this.orginVerts[i];
        if(v.distanceToSquared(orginVert)> 0.00001){
          v.y += (orginVert.y - v.y) * (Math.random() * 0.5 + 0.5) * delay;
          if(v.distanceToSquared(orginVert)< 0.00001){
            v.copy(orginVert);
            this.readyCount ++;
          }
        }
      }
      if(this.readyCount >= this.vertices.length){
        this.isDown = true;
        this.delay =  3 * (Math.random()*0.5 + 0.5);
        this.readyCount = 0;
      }
      this.particleGeo.verticesNeedUpdate = true;
    }
  }

  onresize(e: UIEvent) {
    super.onresize(e);
    console.log('onresive');
    if (this.renderer) {
      this.renderer.setDrawingBufferSize(
        this.clientWidth,
        this.clientHeight,
        1
      );
      if (this.camera && this.camera instanceof THREE.PerspectiveCamera) {
        this.camera.aspect = this.clientWidth / this.clientHeight;
        this.camera.updateProjectionMatrix();
      }
    }
  }

  protected resetCamera(){
    this.camera.position.set(5, 5,5);
    this.camera.lookAt(worldCenter);
  }

  public onkeydown(e: KeyboardEvent): any | void {
    if (e.key === 'r' || ' ') {
      this.resetCamera();
    }
  }
  public oncontextmenu(e: MouseEvent): any | void {
    e.cancelBubble = true;
    e.returnValue = false;
    return false;
  }

  destory() {
    this.geos.forEach((v) => v.dispose());
    this.geos = [];
    this.mats.forEach((v) => v.dispose());
    this.mats = [];
    this.texs.forEach((v) => v.dispose());
    this.texs = [];
    super.destory();
  }
}

export default new _12Particles();
